use std::{collections::HashMap, io::Write, time::Duration};

use af_util::{Artifact, AssetIndex, ClientJar, TrueVersionManifest, Version, VersionManifest};
use anyhow::{Context, bail};
use reqwest;
use serde::{Deserialize, Serialize};
use serde_json::json;
use tokio::{fs, io::AsyncWriteExt, time::sleep};
#[derive(Serialize)]
struct RpcRequest<'a> {
    jsonrpc: &'a str,
    method: &'a str,
    params: Vec<serde_json::Value>,
    id: &'a str,
}
#[derive(Debug, Deserialize, Serialize)]
struct aria2_tellStatus {
    gid: String,
    status: String,
}
#[derive(Deserialize, Serialize, Debug)]
struct RpcResponse {
    result: Option<RpcResult>,
    message: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
enum RpcResult {
    String(String),
    Object(aria2_tellStatus),
}
const RPC_URL: &str = "http://127.0.0.1:6800/jsonrpc";
pub async fn get_version_manifest() -> anyhow::Result<VersionManifest> {
    let url = "https://piston-meta.mojang.com/mc/game/version_manifest.json";
    let client = reqwest::Client::builder()
        .timeout(Duration::from_secs(10))
        .build()?;
    let response = client.get(url).send().await?.error_for_status()?;
    let manifest = response.json().await?;
    Ok(manifest)
}
/*
/// DOWNLOAD USE ARIA2 RPC
pub async fn download_artifacts(files: Vec<Artifact>) -> anyhow::Result<()> {
    //DOWNLOAD ALL FILES TO .minecraft/objects/


    let batch = files
        .iter()
        .map(|a| {
            json!({
                "methodName": "aria2.addUri",
                "params": [
                "token:af",
                        [a.url],
                        {
                            "dir": ".minecraft/libobjects",
                            "out": a.sha1,          // 保存文件名
                            "checksum": format!("sha-1={}", a.sha1), // aria2 原生校验
                            "continue": true,
                            "max-connection-per-server": 16
                        }
                ]
            })
        })
        .collect();
    client.call(1, "system.multicall", batch).await?;
    Ok(())
}
*/
pub async fn download_asset_index(index: AssetIndex) -> anyhow::Result<String> {
    let client = reqwest::Client::builder()
        .timeout(Duration::from_secs(10))
        .build()?;
    let bytes = client
        .get(index.url)
        .send()
        .await?
        .error_for_status()?
        .bytes()
        .await?;
    fs::create_dir_all(".minecraft/assets/indexes").await?;
    let mut file = fs::File::create(format!(".minecraft/assets/indexes/{}.json", index.id)).await?;
    file.write(&bytes);
    String::from_utf8(bytes.to_vec()).context("Not UTF-8")
}
#[derive(Deserialize, Debug)]
#[allow(dead_code)]
pub struct AssetObject {
    hash: String,
    size: u64,
}
///DOWNLOAD ALL ASSETS TO .minecraft/assets/objects/ USE ARIA2 RPC
pub async fn download_assets(obj: Vec<AssetObject>) -> anyhow::Result<()> {
    let client = reqwest::Client::builder()
        .timeout(Duration::from_secs(10))
        .build()?;
    let tasks = obj
        .iter()
        .map(|item| {
            let url = format!(
                "https://resources.download.minecraft.net/{}/{}",
                &item.hash[0..2],
                item.hash
            );
            RpcRequest {
                jsonrpc: "2.0",
                id: "",
                method: "aria2.addUri",
                params: vec![
                    json!("token:af"),
                    json!([url]),
                    json!({
                        "dir": format!(".minecraft/assets/objects/{}/", &item.hash[0..2]),
                        "out": item.hash,
                        "continue": true,
                        "max-connection-per-server": 16
                    }),
                ],
            }
        })
        .collect::<Vec<_>>();
    let batch = RpcRequest {
        jsonrpc: "2.0",
        id: "",
        method: "system.multicall",
        params: vec![serde_json::to_value(tasks)?],
    };
    let resp = client.post(RPC_URL).json(&batch).send().await?;
    if !resp.status().is_success() {
        bail!("RPC error: {}", resp.text().await?);
    }
    let resp = resp.text().await?;
    eprintln!("{}", resp);
    let resp: RpcResponse = serde_json::from_str(&resp)?;

    Ok(())
}

pub async fn download_version(v: Version) -> anyhow::Result<()> {
    let client = reqwest::Client::builder()
        .timeout(Duration::from_secs(10))
        .build()?;
    let response = client.get(v.url).send().await?.error_for_status()?;
    let data: serde_json::Value = response.json().await?;
    let version_dir = format!(".minecraft/versions/{}/", v.id);
    fs::create_dir_all(&version_dir).await?;
    let mut file = fs::File::create(format!("{}{}.json", version_dir, v.id)).await?;
    file.write_all(data.to_string().as_bytes()).await?;
    println!("Downloaded version manifest for version {}", v.id);
    let manifest: TrueVersionManifest = serde_json::from_value(data.clone())?;
    let client_jar: ClientJar = serde_json::from_value(data["downloads"]["client"].clone())?;
    let req = RpcRequest {
        jsonrpc: "2.0",
        method: "aria2.addUri",
        id: "",
        params: vec![
            json!("token:af"),
            json!([client_jar.url]),
            json!({
                "dir": version_dir,
                "out": format!("{}-client.jar", v.id),
                "continue": true,"allow-overwrite":false,
                "max-connection-per-server": 16,
                "checksum": format!("sha-1={}", client_jar.sha1)
            }),
        ],
    };

    let resp = client.post(RPC_URL).json(&req).send().await?;
    if resp.status() != 200 {
        bail!("RPC error: {}", resp.status());
    }
    let resp = resp.text().await?;
    let resp: RpcResponse = serde_json::from_str(&resp)?;
    if let Some(gid) = resp.result {
        loop {
            let check_req = RpcRequest {
                jsonrpc: "2.0",
                method: "aria2.tellStatus",
                id: "",
                params: vec![json!("token:af"), json!(gid), json!(["gid", "status"])],
            };
            let check_resp = client.post(RPC_URL).json(&check_req).send().await?;

            if check_resp.status() != 200 {
                bail!("RPC error: {}", check_resp.status());
            }
            let check_resp = check_resp.text().await?;
            eprintln!("{}", check_resp);
            let state: RpcResponse = serde_json::from_str(&check_resp)?;
            if let Some(state) = state.result {
                if let RpcResult::Object(state) = state {
                    if state.status == "complete" {
                        break;
                    }
                }
            }
            sleep(Duration::from_secs(1)).await;
        }
    } else if let Some(r) = resp.message {
        bail!(r)
    }
    println!("Downloaded client jar for version {}", v.id);

    let i = manifest.assetIndex;
    let asset_str = download_asset_index(i).await?;
    println!("Downloaded asset index for version {}", v.id);
    #[derive(Deserialize)]
    struct Asset {
        objects: HashMap<String, AssetObject>,
    }
    let assets_obj: Asset = serde_json::from_str(&asset_str)?;
    let assets: Vec<AssetObject> = assets_obj.objects.into_values().collect();
    download_assets(assets).await?;
    println!("Downloaded assets for version {}", v.id);
    /*
    let artifacts: Vec<Artifact> = manifest.libraries.into_iter().map(|lib| lib.downloads.artifact).collect();
    download_artifacts(artifacts).await?;
    println!("Downloaded libraries for version {}", v.id);
    */
    Ok(())
}
